TypeScript 97.5%
SQL 1.4%
Python 0.8%
1import Link from "next/link";2import { notFound } from "next/navigation";3import { ArrowLeft } from "lucide-react";4import { requireAdmin } from "@/lib/session";5import { getRequestDetail } from "@/lib/queries/admin";6import { formatBytes, formatDate, formatMs, formatUsd } from "@/lib/format";7import { PageHeader } from "@/components/ui/page-header";8import { Badge, StatusBadge } from "@/components/ui/badge";9import { Button } from "@/components/ui/button";10import { Stat, StatGrid } from "@/components/ui/stat";11import { CodeBlock } from "@/components/ui/code-block";12import { CopyButton } from "@/components/ui/copy-button";13import { Table, TableBody, TableCell, TableEmpty, TableHead, TableHeader, TableRow } from "@/components/ui/table";14import { KV, MarginText, Mono, NetworkBadge, Panel, PlanBadge, ProviderBadge, numCell } from "@/components/admin/primitives";1516export const dynamic = "force-dynamic";1718export default async function AdminRequestDetail({ params }: { params: Promise<{ id: string }> }) {19 await requireAdmin();20 const { id } = await params;21 const d = await getRequestDetail(id);22 if (!d) notFound();23 const { req } = d;24 const margin = req.priceUsd - req.costUsd;2526 return (27 <>28 <div className="mb-4">29 <Button asChild variant="ghost" size="sm" className="-ml-2">30 <Link href="/admin/requests">31 <ArrowLeft className="size-3.5" /> Requests32 </Link>33 </Button>34 </div>35 <PageHeader36 eyebrow="Request"37 title={38 <span className="flex flex-wrap items-center gap-2">39 <Mono className="text-[18px]">{req.id}</Mono>40 <CopyButton value={req.id} size="icon-sm" className="size-6" />41 <StatusBadge status={req.status} />42 {req.errorCode ? <Badge variant="danger">{req.errorCode}</Badge> : null}43 {req.cached ? <Badge variant="info">cached</Badge> : null}44 </span>45 }46 description={47 <span className="break-all">48 {req.method} {req.url}49 </span>50 }51 actions={52 <>53 {d.org ? (54 <Button asChild variant="outline" size="sm">55 <Link href={`/admin/organizations/${d.org.id}`}>Organization</Link>56 </Button>57 ) : null}58 <Button asChild variant="outline" size="sm">59 <Link href={`/admin/domains/${encodeURIComponent(req.domain)}`}>Domain profile</Link>60 </Button>61 </>62 }63 />6465 <StatGrid cols={6} className="mb-6">66 <Stat label="HTTP status" value={req.httpStatus ?? "—"} />67 <Stat label="Attempts" value={req.attempts} />68 <Stat label="Latency" value={formatMs(req.latencyMs)} />69 <Stat label="Bytes in / out" value={`${formatBytes(req.bytesIn)} / ${formatBytes(req.bytesOut)}`} />70 <Stat label="Upstream cost → price" value={`${formatUsd(req.costUsd, true)} → ${formatUsd(req.priceUsd, true)}`} />71 <Stat label="Margin" value={<MarginText value={margin} pct={req.priceUsd > 0 ? (margin / req.priceUsd) * 100 : null} />} />72 </StatGrid>7374 <div className="grid gap-6 lg:grid-cols-2">75 <Panel title="Request">76 <KV77 cols={2}78 items={[79 { label: "Organization", value: d.org ? <span className="inline-flex items-center gap-2"><Link href={`/admin/organizations/${d.org.id}`} className="hover:underline">{d.org.name}</Link><PlanBadge plan={d.org.plan} />{d.org.providerVisibility ? <Badge variant="warning">provider debug on</Badge> : null}</span> : req.organizationId },80 { label: "Project", value: d.project ? `${d.project.name} (${d.project.environment})` : req.projectId },81 { label: "API key", value: d.key ? <span>{d.key.name} <Mono className="text-fg-muted">{d.key.keyPrefix}…{d.key.last4}</Mono> <StatusBadge status={d.key.mode} /></span> : req.source === "playground" ? "— (playground)" : "—" },82 { label: "Source", value: req.source },83 { label: "URL", value: <span className="break-all">{req.url}</span>, mono: true, span: true },84 { label: "Final URL", value: req.finalUrl ? <span className="break-all">{req.finalUrl}</span> : "—", mono: true, span: true },85 { label: "Domain", value: req.domain, mono: true },86 { label: "Method", value: req.method, mono: true },87 { label: "Requested network", value: req.requestedNetwork },88 { label: "Resolved network", value: <NetworkBadge network={req.network} /> },89 { label: "Geo", value: [req.country, req.region, req.city].filter(Boolean).join(" / ") || "—", mono: true },90 { label: "Session", value: req.sessionId ?? "—", mono: true },91 { label: "Browser", value: req.browser ? "requested" : "no" },92 { label: "Format", value: req.format },93 { label: "Client IP", value: req.clientIp ?? "—", mono: true },94 { label: "User agent", value: req.userAgent ?? "—", span: true },95 { label: "Created", value: formatDate(req.createdAt) },96 { label: "Completed", value: req.completedAt ? formatDate(req.completedAt) : "—" },97 { label: "Error message", value: req.errorMessage ?? "—", span: true },98 ]}99 />100 </Panel>101 <div className="grid gap-6">102 <Panel title="Timing" description="Milliseconds, as recorded by the gateway.">103 {req.timing && Object.keys(req.timing).length ? (104 <KV cols={3} items={Object.entries(req.timing).map(([k, v]) => ({ label: k, value: formatMs(v), mono: true }))} />105 ) : (106 <p className="text-[13px] text-fg-muted">No timing recorded.</p>107 )}108 </Panel>109 {d.session ? (110 <Panel title="Proxy session" description="Upstream sticky session used by this request.">111 <KV112 cols={2}113 items={[114 { label: "Session", value: d.session.id, mono: true },115 { label: "Provider", value: <ProviderBadge id={d.session.provider} /> },116 { label: "Network", value: <NetworkBadge network={d.session.network} /> },117 { label: "Status", value: <StatusBadge status={d.session.status} /> },118 { label: "Requests", value: d.session.requestCount },119 { label: "Expires", value: formatDate(d.session.expiresAt) },120 ]}121 />122 </Panel>123 ) : null}124 <Panel title="Usage events" flush>125 <Table>126 <TableHeader>127 <TableRow>128 <TableHead>Metric</TableHead>129 <TableHead className="text-right">Quantity</TableHead>130 <TableHead className="text-right">Price</TableHead>131 <TableHead className="text-right">Upstream</TableHead>132 </TableRow>133 </TableHeader>134 <TableBody>135 {d.usage.length === 0 ? (136 <TableEmpty colSpan={4}>No usage events (free-tier or failed before metering).</TableEmpty>137 ) : (138 d.usage.map((u) => (139 <TableRow key={u.id}>140 <TableCell>141 <Mono>{u.metric}</Mono>142 </TableCell>143 <TableCell className={numCell}>144 {u.unit === "bytes" ? formatBytes(u.quantity) : `${u.quantity} ${u.unit}`}145 </TableCell>146 <TableCell className={numCell}>{formatUsd(u.costUsd, true)}</TableCell>147 <TableCell className={numCell}>{formatUsd(u.upstreamCostUsd, true)}</TableCell>148 </TableRow>149 ))150 )}151 </TableBody>152 </Table>153 </Panel>154 </div>155 </div>156157 <Panel title="Attempts" description="Every upstream attempt with provider, routing score and internal error detail. Never shown to customers." flush className="mt-6">158 <Table>159 <TableHeader>160 <TableRow>161 <TableHead>#</TableHead>162 <TableHead>Provider</TableHead>163 <TableHead>Network</TableHead>164 <TableHead>Geo</TableHead>165 <TableHead>Outcome</TableHead>166 <TableHead className="text-right">HTTP</TableHead>167 <TableHead>Error / block reason</TableHead>168 <TableHead className="text-right">Score</TableHead>169 <TableHead className="text-right">Duration</TableHead>170 <TableHead className="text-right">Bytes</TableHead>171 <TableHead className="text-right">$/GB</TableHead>172 <TableHead className="text-right">Cost</TableHead>173 </TableRow>174 </TableHeader>175 <TableBody>176 {d.attempts.length === 0 ? (177 <TableEmpty colSpan={12}>No attempts recorded (request rejected before routing).</TableEmpty>178 ) : (179 d.attempts.map((a) => (180 <TableRow key={a.id}>181 <TableCell className={numCell}>{a.attemptNo}</TableCell>182 <TableCell>183 <ProviderBadge id={a.provider} />184 </TableCell>185 <TableCell>186 <NetworkBadge network={a.network} />187 </TableCell>188 <TableCell>189 <Mono>{a.country ?? "—"}</Mono>190 {a.sessionKey ? <div className="font-mono text-[11px] text-fg-subtle">sticky</div> : null}191 </TableCell>192 <TableCell>193 <StatusBadge status={a.outcome} />194 </TableCell>195 <TableCell className={numCell}>{a.httpStatus ?? "—"}</TableCell>196 <TableCell className="max-w-[320px]">197 {a.errorCode ? <div className="font-mono text-[12px] text-danger">{a.errorCode}</div> : null}198 {a.blockReason ? <div className="text-[12px] text-fg-muted">block: {a.blockReason}</div> : null}199 {a.errorDetail ? (200 <div className="truncate font-mono text-[11.5px] text-fg-subtle" title={a.errorDetail}>201 {a.errorDetail}202 </div>203 ) : null}204 {!a.errorCode && !a.blockReason && !a.errorDetail ? <span className="text-fg-subtle">—</span> : null}205 </TableCell>206 <TableCell className={numCell}>{a.routingScore !== null ? a.routingScore.toFixed(3) : "—"}</TableCell>207 <TableCell className={numCell}>{formatMs(a.durationMs)}</TableCell>208 <TableCell className={numCell}>{formatBytes(a.bytesIn + a.bytesOut)}</TableCell>209 <TableCell className={numCell}>{a.unitPricePerGb ? `$${a.unitPricePerGb}` : "—"}</TableCell>210 <TableCell className={numCell}>{formatUsd(a.costUsd, true)}</TableCell>211 </TableRow>212 ))213 )}214 </TableBody>215 </Table>216 </Panel>217218 <div className="mt-6 grid gap-6 lg:grid-cols-2">219 <Panel title="Request headers" description="Sensitive headers are redacted at ingestion.">220 <CodeBlock code={JSON.stringify(req.requestHeaders ?? {}, null, 2)} lang="json" maxHeight={320} />221 </Panel>222 <Panel title="Response headers">223 <CodeBlock code={JSON.stringify(req.responseHeaders ?? {}, null, 2)} lang="json" maxHeight={320} />224 </Panel>225 </div>226 {d.attempts.some((a) => a.timing) ? (227 <Panel title="Attempt timing (raw)" className="mt-6">228 <CodeBlock code={JSON.stringify(d.attempts.map((a) => ({ attempt: a.attemptNo, provider: a.provider, timing: a.timing })), null, 2)} lang="json" maxHeight={320} />229 </Panel>230 ) : null}231 </>232 );233}234